/* @flow */

import config from '../config'
// 带有三个任意类型参数的函数
import { noop } from 'shared/util'

export let warn = noop
export let tip = noop
export let generateComponentTrace = (noop: any) // work around flow check
export let formatComponentName = (noop: any)

// 开发模式下执行
if (process.env.NODE_ENV !== 'production') {
  // console 是否存在
  const hasConsole = typeof console !== 'undefined'
  // 匹配首字母 或 -字母
  /**
   * 正则回忆练习
   * . 匹配除了换行符以外的任意字符
   * * *前边的内容可以连续重复使用任意次以使整个表达式得到匹配
   * \b 单词的开头或结尾，也就是单词的分界处
   * \d 匹配一位数字(0，或1，或2，或……)
   * 0\d{2}-\d{8} => {2}({8})的意思是前面\d必须连续重复匹配2次(8次)
   * 
   * \s 匹配任意的空白符，包括空格，制表符(Tab)，换行符，中文全角空格
   * \w 匹配字母或数字或下划线或汉字等
   * \d+ 匹配1个或更多连续的数字
   * ^ 匹配字符串的开始
   * $ 匹配字符串的结束
   * 
   * * 重复零次或更多次
   * + 重复一次或更多次
   * ? 重复零次或一次
   * {n} 重复n次
   * {n,} 重复n次或更多次
   * {n,m} 重复n到m次
   * 
   * [aeiou] 匹配任何一个英文元音字母  [] 是或
   * 
   * 反义代码
   * \W 匹配任意不是字母，数字，下划线，汉字的字符
   * \S 匹配任意不是空白符的字符
   * \D 匹配任意非数字的字符
   * \B 匹配不是单词开头或结束的位置
   * [^x] 匹配除了x以外的任意字符
   * [^aeiou] 匹配除了aeiou这几个字母以外的任意字符
   * 
   * <a[^>]+> 匹配用尖括号括起来的以a开头的字符串。
   * 
   */
  const classifyRE = /(?:^|[-_])(\w)/g
  // 转换大写去除-_
  const classify = str => str
    .replace(classifyRE, c => c.toUpperCase())
    .replace(/[-_]/g, '')
  
  // 警告提示
  warn = (msg, vm) => {
    const trace = vm ? generateComponentTrace(vm) : ''

    // 警告函数是否存在
    if (config.warnHandler) {
      config.warnHandler.call(null, msg, vm, trace)
    } else if (hasConsole && (!config.silent)) {
      console.error(`[Vue warn]: ${msg}${trace}`)
    }
  }

  tip = (msg, vm) => {
    if (hasConsole && (!config.silent)) {
      console.warn(`[Vue tip]: ${msg}` + (
        vm ? generateComponentTrace(vm) : ''
      ))
    }
  }

  /*格式化组件名*/
  formatComponentName = (vm, includeFile) => {
    if (vm.$root === vm) {
      return '<Root>'
    }
    const options = typeof vm === 'function' && vm.cid != null
      ? vm.options
      : vm._isVue
        ? vm.$options || vm.constructor.options
        : vm || {}
    let name = options.name || options._componentTag
    const file = options.__file
    if (!name && file) {
      // 出现分组 返回本次匹配的全部分组
      const match = file.match(/([^/\\]+)\.vue$/)
      name = match && match[1]
    }

    return (
      (name ? `<${classify(name)}>` : `<Anonymous>`) +
      (file && includeFile !== false ? ` at ${file}` : '')
    )
  }

  const repeat = (str, n) => {
    let res = ''
    while (n) {
      if (n % 2 === 1) res += str
      if (n > 1) str += str
      n >>= 1
    }
    return res
  }

  generateComponentTrace = vm => {
    if (vm._isVue && vm.$parent) {
      const tree = []
      let currentRecursiveSequence = 0
      while (vm) {
        if (tree.length > 0) {
          const last = tree[tree.length - 1]
          if (last.constructor === vm.constructor) {
            currentRecursiveSequence++
            vm = vm.$parent
            continue
          } else if (currentRecursiveSequence > 0) {
            tree[tree.length - 1] = [last, currentRecursiveSequence]
            currentRecursiveSequence = 0
          }
        }
        tree.push(vm)
        vm = vm.$parent
      }
      return '\n\nfound in\n\n' + tree
        .map((vm, i) => `${
          i === 0 ? '---> ' : repeat(' ', 5 + i * 2)
        }${
          Array.isArray(vm)
            ? `${formatComponentName(vm[0])}... (${vm[1]} recursive calls)`
            : formatComponentName(vm)
        }`)
        .join('\n')
    } else {
      return `\n\n(found in ${formatComponentName(vm)})`
    }
  }
}
